#if !defined(_WIN32)

#include "logger.h"
#include "watcher.h"

#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <csignal>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <memory>
#include <sstream>

static int nCpuIdx = -1;
static int nVszIdx = -1;
static int nResIdx = -1;

static void GetKeyIdx() {
  static bool bCalc = false;
  if (bCalc)
    return;
  bCalc = true;

  FILE *pPipe = popen("ps -u -p 1", "r");
  if (!pPipe)
    return;

  std::string sResult;
  sResult.reserve(4096);

  char pBuf[2048] = {0};
  while (!feof(pPipe)) {
    size_t nSize = fread(pBuf, 1, 2048, pPipe);
    if (nSize > 0)
      sResult.append(pBuf, nSize);
  }

  std::istringstream issH(sResult);
  int nIdx = 0;

  while (issH.good()) {
    std::string sKey;
    issH >> sKey;
    if (sKey == "%CPU")
      nCpuIdx = nIdx;
    else if (sKey == "VSZ")
      nVszIdx = nIdx;
    else if (sKey == "RSS")
      nResIdx = nIdx;
    nIdx++;
  }

  pclose(pPipe);
}

Watcher &Watcher::Instance() {
  static std::unique_ptr<Watcher> iIns;
  if (!iIns)
    iIns.reset(new Watcher);
  GetKeyIdx();
  return *iIns;
}

void Watcher::Breath() {
  static time_t nLastBreath = 0;
  static char pBuf[2048] = {0};

  time_t nTick = time(NULL);
  if (nTick == nLastBreath)
    return;
  nLastBreath = nTick;

  Json::Value iMsg;
  iMsg["action"] = "update";
  iMsg["watchers"] = Json::Value(Json::arrayValue);

  for (auto &kv : _mWatcher) {
    if (kv.second.emStatus == EStatus::Running) {
      int nStatus = 0;
      int nChild = waitpid(kv.second.nPid, &nStatus, WNOHANG);

      if (nChild > 0) {
        Tail(kv.first, kv.second);
        CatchExit(kv.first, kv.second,
                  WEXITSTATUS(nStatus) == 0 ? EStatus::Stopped : EStatus::Fatal,
                  true);
        continue;
      } else if (nChild == 0) {
        Tail(kv.first, kv.second);

        std::string sCmd = "ps -u -p " + std::to_string(kv.second.nPid);
        std::string sResult;

        sResult.reserve(1024 * 8);

        FILE *pPipe = popen(sCmd.data(), "r");
        if (!pPipe)
          continue;

        while (!feof(pPipe)) {
          size_t nSize = fread(pBuf, 1, 2048, pPipe);
          if (nSize > 0)
            sResult.append(pBuf, nSize);
        }

        pclose(pPipe);

        int nVsz = 0, nRes = 0, nIdx = 0;
        std::string sCpu = "";
        std::istringstream iss(sResult);

        while (iss.get() != '\n')
          continue;
        while (iss.good()) {
          std::string sData;
          iss >> sData;
          if (nIdx == nCpuIdx) {
            sCpu = sData;
          } else if (nIdx == nVszIdx) {
            nVsz = atoi(sData.data());
          } else if (nIdx == nResIdx) {
            nRes = atoi(sData.data());
          }

          nIdx++;
        }

        Json::Value iStatus;
        iStatus["name"] = kv.first;
        iStatus["cpu"] = sCpu;
        iStatus["vsz"] = nVsz;
        iStatus["res"] = nRes;

        iMsg["watchers"].append(iStatus);
      } else {
        CatchExit(kv.first, kv.second,
                  errno == EINTR ? EStatus::Stopped : EStatus::Fatal, true);
      }
    }
  }

  if (iMsg["watchers"].size() > 0)
    Notify("/ws", iMsg);
}

void Watcher::Start(const std::string &sName, const std::string &sPath,
                    const std::string &sCmd, int nRetry) {
  auto it = _mWatcher.find(sName);
  Info *pInfo = nullptr;

  if (it != _mWatcher.end() && it->second.emStatus == EStatus::Running)
    return;
  if (it == _mWatcher.end()) {
    Info iInfo;
    iInfo.nRetry = nRetry;
    iInfo.sCmd = sCmd;
    iInfo.sPath = sPath;

    _mWatcher[sName] = iInfo;
    pInfo = &_mWatcher[sName];
  } else if (it->second.emStatus == EStatus::Running) {
    return;
  } else {
    pInfo = &it->second;
    pInfo->nRetry = nRetry;
  }

  pInfo->nStart = time(NULL);

  std::vector<std::string> vParams;
  std::string sDeli = " \t";

  std::string::size_type nStart = 0, nEnd = 0;
  while (nEnd < sCmd.length()) {
    char nTemp = sCmd[nEnd];
    if (sDeli.find(nTemp) != std::string::npos) {
      if (nStart != nEnd)
        vParams.push_back(sCmd.substr(nStart, nEnd - nStart));
      ++nEnd;
      nStart = nEnd;
    } else {
      ++nEnd;
    }
  }

  if (nStart != nEnd)
    vParams.push_back(sCmd.substr(nStart));
  if (vParams.size() <= 0)
    return;

  int pFd[2] = {0, 0};
  if (pipe(pFd) == -1) {
    LOG_ERR("Failed to create pipe for start watcher [%s]", sName.data());
    return;
  }

  pid_t nChildPid = fork();
  if (nChildPid == -1) {
    LOG_ERR("Start %s ... [Failed] Path : %s, Cmd : %s", sName.data(),
            sPath.data(), sCmd.data());
    pInfo->emStatus = EStatus::Fatal;
    pInfo->nPid = -1;
  } else if (nChildPid == 0) {
    close(pFd[0]);
    dup2(pFd[1], STDERR_FILENO);
    dup2(pFd[1], STDOUT_FILENO);
    setpgid(nChildPid, nChildPid);

    if (-1 == chdir(sPath.data())) {
      std::cerr << "Failed to change directory : " << sPath << std::endl;
    } else {
      char *pParam[20] = {0};
      memset(pParam, 0, sizeof(pParam));

      for (int i = 0; i < vParams.size() && i < 19; ++i)
        pParam[i] = (char *)vParams[i].data();

      if (-1 == execvp(vParams[0].data(), pParam))
        std::cerr << "No such file named : " << vParams[0] << std::endl;
    }

    exit(-1);
  } else {
    close(pFd[1]);
    int nFlags = fcntl(pFd[0], F_GETFL, 0);
    fcntl(pFd[0], F_SETFL, O_NONBLOCK | nFlags);

    pInfo->nFd = pFd[0];
    pInfo->nPid = nChildPid;
    pInfo->emStatus = EStatus::Running;

    LOG_INFO("Start %s ... [OK]. FD : %d, PID : %d", sName.c_str(), pInfo->nFd,
             pInfo->nPid);
  }

  Json::Value iMsg;
  iMsg["action"] = "status_change";
  iMsg["watcher"] = sName;
  iMsg["status"] = pInfo->emStatus;
  iMsg["pid"] = pInfo->nPid;
  iMsg["start_time"] = pInfo->GetStartTime();

  AppendTail(sName, *pInfo, "\n\n----------- Start -----------\n");
  Notify("/ws", iMsg);
}

void Watcher::Stop(const std::string &sName) {
  auto it = _mWatcher.find(sName);
  if (it == _mWatcher.end() || it->second.emStatus != EStatus::Running)
    return;

  int nStatus = kill(it->second.nPid, SIGINT);
  if (nStatus == -1) {
    LOG_ERR("Stop %s ... [Failed]", sName.data());
  } else {
    nStatus = 0;
    Tail(sName, it->second);
    while (waitpid(it->second.nPid, &nStatus, 0) == -1) {
      if (errno != EINTR)
        break;
    }

    CatchExit(sName, it->second,
              WEXITSTATUS(nStatus) == 0 ? EStatus::Stopped : EStatus::Fatal);
    LOG_INFO("Stop %s ... [OK]", sName.data());
  }
}

void Watcher::StopAll() {
  int nStatus = 0;

  for (auto &kv : _mWatcher) {
    if (kv.second.emStatus == EStatus::Running) {
      kill(kv.second.nPid, SIGINT);
      while (waitpid(kv.second.nPid, &nStatus, 0) == -1) {
        if (errno != EINTR)
          break;
      }
    }
  }
}

void Watcher::Remove(const std::string &sName) {
  auto it = _mWatcher.find(sName);
  if (it == _mWatcher.end())
    return;

  if (it->second.emStatus == EStatus::Running)
    Stop(sName);
  _mWatcher.erase(it);
}

Watcher::Info *Watcher::Get(const std::string &sName) {
  auto it = _mWatcher.find(sName);
  if (it == _mWatcher.end())
    return nullptr;
  return &it->second;
}

void Watcher::Notify(const std::string &sScope, const Json::Value &rMsg) {
  if (_fNotifier)
    _fNotifier(sScope, rMsg);
}

void Watcher::Tail(const std::string &sName, Info &rInfo) {
  static char pLine[8192] = {0}, pData[8192] = {0};

  memset(pLine, 0, 8192);
  memset(pData, 0, 8192);

  int nSize = read(rInfo.nFd, pLine, 8192);
  if (nSize > 0) {
    int nRead = 0;
    for (int i = 0; i < nSize; ++i) {
      if (pLine[i] == 0x1B && pLine[i + 1] == '[') {
        while (i < nSize && pLine[i] != 'm')
          i++;
      } else {
        pData[nRead] = pLine[i];
        ++nRead;
      }
    }

    memcpy(rInfo.pTail + rInfo.nTail, pData, nRead);

    if (rInfo.nTail + nRead > 8192) {
      int nIdx = rInfo.nTail + nRead - 8192;
      int i = 0;

      for (; nIdx < rInfo.nTail + nRead; ++nIdx) {
        if (rInfo.pTail[nIdx] == '\n') {
          nIdx++;
          break;
        }
      }

      for (; i < rInfo.nTail + nRead - nIdx; ++i) {
        rInfo.pTail[i] = rInfo.pTail[i + nIdx];
      }

      rInfo.pTail[i] = '\0';
      rInfo.nTail = i;
    } else {
      rInfo.nTail += nRead;
    }

    Json::Value iMsg;
    iMsg["action"] = "tail_append";
    iMsg["watcher"] = sName;
    iMsg["data"] = pData;

    Notify("/tail/" + sName, iMsg);
    Notify("/tail_all", iMsg);
  }
}

void Watcher::AppendTail(const std::string &sName, Info &rInfo,
                         const std::string &sTail) {
  int nSize = sTail.size();
  memcpy(rInfo.pTail + rInfo.nTail, sTail.data(), nSize);

  if (rInfo.nTail + nSize > 8192) {
    int nIdx = rInfo.nTail + nSize - 8192;
    int i = 0;

    for (; nIdx < rInfo.nTail + nSize; ++nIdx) {
      if (rInfo.pTail[nIdx] == '\n') {
        nIdx++;
        break;
      }
    }

    for (; i < rInfo.nTail + nSize - nIdx; ++i) {
      rInfo.pTail[i] = rInfo.pTail[i + nIdx];
    }

    rInfo.pTail[i] = '\0';
    rInfo.nTail = i;
  } else {
    rInfo.nTail += nSize;
  }

  Json::Value iMsg;
  iMsg["action"] = "tail_append";
  iMsg["data"] = sTail;

  Notify("/tail/" + sName, iMsg);
}

void Watcher::CatchExit(const std::string &sName, Info &rInfo, EStatus emStatus,
                        bool bRetry /* = false */) {
  close(rInfo.nFd);

  rInfo.emStatus = emStatus;
  rInfo.nFd = -1;
  rInfo.nPid = -1;
  rInfo.nStart = 0;

  Json::Value iMsg;
  iMsg["action"] = "status_change";
  iMsg["watcher"] = sName;
  iMsg["status"] = rInfo.emStatus;
  iMsg["pid"] = rInfo.nPid;
  iMsg["start_time"] = rInfo.GetStartTime();

  if (bRetry && rInfo.nRetry > 0) {
    rInfo.emStatus = EStatus::Retry;
    iMsg["status"] = EStatus::Retry;

    Notify("/ws", iMsg);
    AppendTail(sName, rInfo, "\n----------- Auto-retry -----------\n");

    Start(sName, rInfo.sPath, rInfo.sCmd, rInfo.nRetry - 1);
  } else {
    Notify("/ws", iMsg);
    AppendTail(sName, rInfo, "\n----------- Stopped -----------\n");
  }
}

#endif